package org.redmagic.service;

import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Set;

import org.redmagic.Request;
import org.redmagic.domain.Property;
import org.redmagic.service.dao.MagicDao;
import org.redmagic.service.spring.CachingRedMagicStringValueResolver;

import com.google.common.base.Optional;
import com.google.common.collect.Sets;

public class MagicHierarchicalService implements HierarchicalService {

	private final MagicDao magicDao;
	
	public MagicHierarchicalService(MagicDao magicDao) {
		this.magicDao = magicDao;
	}
	
	@Override
	public Map<String, String> getHierarchicalProperties(Request request, String user) {
	
		Map<String, String> resolvedProperties = new HashMap<String, String>();
		
		// attempt to resolve each placeholder
		for (String placeholder : request.getPlaceholders()) {
			Optional<String> value = retrieveProperty(placeholder, request.getHierarchy(), user);
			
			// if a value was found for the property, add it
			if (value.isPresent()) {
				resolvedProperties.put(placeholder, value.get());
			}
		}		
		
		return resolvedProperties;
	}

	/**
	 * Run each property through the requested hierarchy, looking for the first possible matching value.
	 * 
	 * @param property
	 * 			The property to try to resolve.
	 * @param hierarchy
	 * 			The ordered hierarchy that should be used to resolve the property.
	 * @return
	 * 			An optional containing the value if it was found, or else an Optional.absent();
	 */
	private Optional<String> retrieveProperty(String property, List<List<String>> hierarchy, String user) {
		
		Optional<String> rval = Optional.absent();
		
		// run through the hierarchy
		for (List<String> tags : hierarchy) {
			Optional<String> result = magicDao.getProperty(property, tags, user);
			
			if (result.isPresent()) {
				CachingRedMagicStringValueResolver resolver = new CachingRedMagicStringValueResolver();
				// resolve nested properties
				String value = resolveNestedProperties(result.get(), hierarchy, resolver, user);
				rval = Optional.fromNullable(value);
				break;
			}
		}
		
		return rval;
	}
	
	private String resolveNestedProperties(String value, List<List<String>> tagsHierarchy, CachingRedMagicStringValueResolver resolver, String user) {
		
		// get all the placeholders
		Set<String> discoveredPlaceholders = resolver.resolveStringValueAsync(value);
		
		// we only want the ones not in the 'known' missing properties list.
		Set<String> placeholders = Sets.difference(discoveredPlaceholders, resolver.getKnownMissingProperties());
		
		Properties properties = new Properties();
		// loop over the tag hierarchy for each placeholder, until we find a value
		for (String placeholder : placeholders) {
			String resolvedValue = null;
			for (List<String> tags : tagsHierarchy) {
				Optional<String> foundValue = magicDao.getProperty(placeholder, tags, user);
				if (foundValue.isPresent()) {
					resolvedValue = foundValue.get();
					break;
				}
			}
			
			// if we are missing any property, fail fast.
			if (resolvedValue != null) {
				properties.put(placeholder, resolvedValue);
			} else {
				resolver.addMissingPlaceholder(placeholder);
			}
		}
		
		resolver.setResolvedProperties(properties);
		String resolvedValue = resolver.resolveStringValue(value);
		
		// check to see if there are any new nested properties
		Set<String> newPlaceholders = resolver.resolveStringValueAsync(resolvedValue);
		
		// get the ones that aren't in the list of known missing properties
		Set<String> remainingPlaceholders = Sets.difference(newPlaceholders, resolver.getKnownMissingProperties());
		
		if (!remainingPlaceholders.isEmpty()) {
			return resolveNestedProperties(resolvedValue, tagsHierarchy, resolver, user);			
		} else {
			return resolvedValue;
		}
	}

	@Override
	public Optional<String> getProperty(String base, List<String> tags, String user) {
		return magicDao.getProperty(base, tags, user);
	}

	@Override
	public boolean createProperty(Property property, String user) {
		return magicDao.createProperty(property, user);
	}

	@Override
	public boolean updateProperty(Property property, String user) {
		return magicDao.updateProperty(property, user);
	}
}
